#include"include.h"

typedef struct {
   // First uint32_t is a session cookie.
   // Second is the offset in which to place data returning from reads.
   uint32_t sessinfo[2];
   tdisk_t *manager;
} tsessionkey_t;

typedef struct {
   int expecting;
   int received;
   void *dataBuffer;
   peer_t *p;
   void (*callback) (peer_t *, void *, int);
} tsession_t;

int tdiskSessionComparator(const void *a, const void *b)
{
   const tsessionkey_t *at = a;
   const tsessionkey_t *bt = b;
   return *((int *) &(at->sessinfo[0])) - *((int *) &(bt->sessinfo[0]));
}

void tdiskReadCallback (void *data, int len, void *cookie, int error)
{
   tsessionkey_t *c = cookie;
   uint32_t bOffset = c->sessinfo[1];
   tdisk_t *manager = c->manager;
   tsession_t *session;
   // Find the session that this belongs to
   if(dictContainsKey(c->manager->readSession, cookie))
   {
      session = dictGet(c->manager->readSession, cookie);
      memcpy(session->dataBuffer + bOffset, data, len);
      session->received++;
   }
   else
   {  // We don't have this session anymore? Just discard the data...
      // Add logging stuff here later.
   }

   if(error || (session->received == session->expecting))
   {  // Callback time!
      session->callback(session->p, session->dataBuffer, error);
      // Clean up this session
      dictRemove(manager->readSession, cookie);
      cookiemanageReleaseCookie(manager->cmanage, c->sessinfo[0]);
      free(session);
   }

   // Clean up extra data buffer
   free(data);
}


tdisk_t *tdiskInit()
{
   tdisk_t *ntdisk = malloc(sizeof(tdisk_t));
   ntdisk->fmanage = filemanageInit();
   ntdisk->cmanage = cookiemanageInit();
   // Initialize other tdisk members
   ntdisk->readSession = newDict(tdiskSessionComparator);

   return ntdisk;
}

void tdiskRun(tdisk_t *t)
{
   filemanageRun(t->fmanage);
}

void tdiskExitAndCleanup(tdisk_t *t)
{
   filemanageExitAndCleanup(t->fmanage);
   // Cleanup everything else
   cookiemanageCleanup(t->cmanage);
   free(t);
}

// Defines a fragment of memory from a file to read
typedef struct {
   char *filename;
   long foffset;
   uint32_t  datalen;

   int buffOffset;
} tmemfrag_t;

/* Returns a list of tmemfrag_ts based on the requested data accesses in the
 * opdata struct passed, as well as the list of files supplied by the torrent
 */ 
list_t *fragmentMemAcess(tdiskop_t opdata)
{
   loggerLog(LOG_INFO, "Request for peice %d at offset %d\n",
      opdata.index, opdata.offset);
   sfile *curr;
   list_t *rlist = createList();
   int nfiles = listGetSize(opdata.filelist);

   // Holds the length of each file up to file i (inclusive)
   long lenSoFar[nfiles];
   // Filename of each file
   char *fNames[nfiles];
   long runningTotal = 0;
   int i = 0;
   listLock(opdata.filelist);
   resetList(opdata.filelist);
   while(!endList(opdata.filelist))
   {
      curr = getObject(opdata.filelist);
      runningTotal += curr->len;
      fNames[i] = curr->name;
      lenSoFar[i++] = runningTotal;
   }
   listRelease(opdata.filelist);
   // Need to find base for buffer
   int bStart = opdata.piecelen * opdata.index + opdata.offset;

   // Begin constructing  tmemfrags
   uint32_t buffOffset = 0;
   uint32_t fOffset;      // Where in file array buffOffset is right now
   uint32_t leftToRead;   // Data left to make fragments for
   uint32_t leftInFile;   // Data left in the file that can be read
   uint32_t cToRead;      // How much to read right now

   int cFOffset;     // Position in current file
   tmemfrag_t *cfrag;

   while(buffOffset < opdata.datalen)
   {
      leftToRead = opdata.datalen - buffOffset;
      fOffset = bStart + buffOffset; 
      loggerLog(LOG_INFO, "File offset of %d\n", fOffset);

      cfrag = malloc(sizeof(tmemfrag_t));
      // Find file to read from
      for(i = 0; i < nfiles && lenSoFar[i] <= fOffset; i++);

      if(i >= nfiles) break; // This shouldn't happen.
      // Where are we in this file anyway?
      if(i == 0)
         cFOffset = fOffset;
      else
         cFOffset = fOffset - lenSoFar[i - 1];

      // Calculate how much to read from this file
      leftInFile = lenSoFar[i] - fOffset;
      if(leftInFile < leftToRead)// && leftInFile != 0) // Read What's left and keep going
         cToRead = leftInFile;
      else // Read what you need and leave
         cToRead = leftToRead;

      // Fill in fragment info
      cfrag->filename = fNames[i];
      cfrag->foffset = cFOffset;
      cfrag->datalen = cToRead;
      cfrag->buffOffset = buffOffset;

      
      loggerLog(LOG_INFO, "\tAccess %d bytes starting at %d in %s\n", cToRead,
         cFOffset, fNames[i]);

      // Add fragment to return list
      addObject(rlist, cfrag);

      buffOffset += cToRead;
   }

   return rlist;
}

void tdisk_writeData(tdiskop_t opdata, void *data)
{
   tdisk_t *manager = opdata.manager;
   list_t *fragList = fragmentMemAcess(opdata);
   // Submit write requests for all of the given fragments
   tmemfrag_t *currFrag;

   char *fn;
   char *bdir;
   char *rfname;
   int rfnamelen;
   long foffs;
   int boffs;
   int dlen;
   resetList(fragList);
   while(!endList(fragList))
   {
      currFrag = getObject(fragList);
      fn = currFrag->filename;
      bdir = opdata.baseDir;
      foffs = currFrag->foffset;
      boffs = currFrag->buffOffset;
      dlen = currFrag->datalen;

      rfnamelen = strlen(bdir) + strlen(fn) + 2;
      rfname = malloc(rfnamelen);
      memset(rfname, '\0', rfnamelen);
      strcpy(rfname, bdir);
      strcat(rfname, "/");
      strcat(rfname, fn);
      filemanageWriteData(manager->fmanage, rfname, foffs, data+boffs, dlen);
      free(rfname);
   }

   // Cleanup
   clearListAndFreeElements(fragList);
}

void tdisk_readData(tdiskop_t opdata, peer_t *p,
      void (*callback) (peer_t *, void *, int))
{
   tdisk_t *manager = opdata.manager;
   // Turn the data read into several file fragment reads
   int fragments;
   list_t *fragList = fragmentMemAcess(opdata);
   fragments = listGetSize(fragList);
   // Generate session key
   tsessionkey_t *sesskey = malloc(sizeof(tsessionkey_t));
   sesskey->sessinfo[0] = cookiemanageNewCookie(manager->cmanage);
   sesskey->sessinfo[1] = 0;

   // Submit read requests for all of the given fragements
   tmemfrag_t *currFrag;

   tsessionkey_t *cCookie;
   char *fn;
   long offs;
   int dlen;
   resetList(fragList);
   while(!endList(fragList))
   {
      currFrag = getObject(fragList);
      // Generate cookie
      cCookie = malloc(sizeof(tsessionkey_t));
      cCookie->sessinfo[0] = sesskey->sessinfo[0];
      cCookie->sessinfo[1] = currFrag->buffOffset;
      cCookie->manager = manager;

      fn = currFrag->filename;
      offs = currFrag->foffset;
      dlen = currFrag->datalen;
      filemanageReadData(manager->fmanage, fn, offs, dlen, cCookie,
         tdiskReadCallback);
   }

   // Store session info
   tsession_t *sess = malloc(sizeof(tsession_t));
   sess->expecting = fragments;
   sess->received = 0;
   sess->dataBuffer = malloc(opdata.datalen);
   sess->p = p;
   sess->callback = callback;

   dictPut(manager->readSession, sesskey, sess);

   // Cleanup
   clearListAndFreeElements(fragList);
}
